// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包test2json实现了测试二进制输出到JSON的转换。
// 它由cmd/test2json和cmd/go使用。
// 
// 有关JSON编码的详细信息，请参阅cmd/test2json文档。
package test2json

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"
)

// 模式控制转换的详细信息。
type Mode int

const (
	Timestamp Mode = 1 << iota // 在事件中包含时间
)

// 事件是我们发出的JSON结构。
type event struct {
	Time    *time.Time `json:",omitempty"`
	Action  string
	Package string     `json:",omitempty"`
	Test    string     `json:",omitempty"`
	Elapsed *float64   `json:",omitempty"`
	Output  *textBytes `json:",omitempty"`
}

// textBytes是一种让JSON以字符串形式发出[]字节而不实际将其复制到字符串的黑客行为。
// 它实现了编码。TextMarshaler以[]字节的形式返回其文本形式，
// 然后json将该文本形式编码为字符串（这是我们的目标）。
type textBytes []byte

func (b textBytes) MarshalText() ([]byte, error) { return b, nil }

// 转换器保存测试到JSON转换的状态。
// 它实现io。写关闭器；调用方将测试输出写入，
// 中，转换器将JSON输出写入w.
type Converter struct {
	w        io.Writer  // JSON输出流
	pkg      string     // 在事件
	mode     Mode       // 模式位
	start    time.Time  // 时间转换器启动
	testName string     // 当前测试的名称，对于输出属性
	report   []*event   // 挂起的测试结果报告（为子测试嵌套）
	result   string     // 如果看到整体测试结果
	input    lineBuffer // 输入缓冲区
	output   lineBuffer // 输出缓冲区
}

// 输入缓冲区和输出缓冲区大小。
// 它们是变量，因此可以在测试期间减少。
// 
// 输入缓冲区需要能够容纳我们想要识别的任何单个测试
// 指令行，比如：
// 
// /<many spaces>--PASS:very/nested/s/u/b/t/e/s/t 
// 
// 如果有人报告测试指令行>4k不工作，
// 建议他们重组测试或测试名称是合理的。
// 
// 输出缓冲区必须大于等于utf8。UTFMax，因此它可以
// 累积任何单个UTF8序列。输出缓冲区中完全符合
// 的行在单个输出事件中发出。
// 否则它们将被拆分为多个事件。因此，输出缓冲区大小限制了单个JSON输出事件的编码大小。1k似乎是想要避免分裂输出线和不想要产生巨大输出事件之间的合理平衡。
var (
	inBuffer  = 4096
	outBuffer = 1024
)

// NewConverter返回一个“test to json”转换器。
// 返回的writer上的写操作以JSON的形式写入w，
// 且延迟最小。
// 
// 对w的写入是以\n，
// 结尾的整个JSON事件，因此可以安全地运行对多个转换器进行写入的多个测试
// 写入单个底层输出流w。
// 只要底层输出w可以处理来自多个Goroutin的并发写入
// 就可以了，结果将是一个JSON流
// 描述所有并发测试中执行的相对顺序。
// 
// 模式标志用于调整转换器的行为。
// 传递ModeTime包括事件时间戳和经过的时间。
// 
// pkg字符串（如果存在）指定JSON流中
// 报表的导入路径。
func NewConverter(w io.Writer, pkg string, mode Mode) *Converter {
	c := new(Converter)
	*c = Converter{
		w:     w,
		pkg:   pkg,
		mode:  mode,
		start: time.Now(),
		input: lineBuffer{
			b:    make([]byte, 0, inBuffer),
			line: c.handleInputLine,
			part: c.output.write,
		},
		output: lineBuffer{
			b:    make([]byte, 0, outBuffer),
			line: c.writeOutputEvent,
			part: c.writeOutputEvent,
		},
	}
	return c
}

// Write将测试输入写入转换器。
func (c *Converter) Write(b []byte) (int, error) {
	c.input.write(b)
	return len(b), nil
}

// Exited将测试进程标记为已退出给定错误。
func (c *Converter) Exited(err error) {
	if err == nil {
		c.result = "pass"
	} else {
		c.result = "fail"
	}
}

var (
	// 在成功运行时通过测试打印。
	bigPass = []byte("PASS\n")

	// 在正常测试失败后通过测试打印。
	bigFail = []byte("FAIL\n")

	// 如果测试二进制文件终止
	// 并出现错误，则由“go test”打印并显示错误。
	bigFailErrorPrefix = []byte("FAIL\t")

	updates = [][]byte{
		[]byte("=== RUN   "),
		[]byte("=== PAUSE "),
		[]byte("=== CONT  "),
	}

	reports = [][]byte{
		[]byte("--- PASS: "),
		[]byte("--- FAIL: "),
		[]byte("--- SKIP: "),
		[]byte("--- BENCH: "),
	}

	fourSpace = []byte("    ")

	skipLinePrefix = []byte("?   \t")
	skipLineSuffix = []byte("\t[no test files]\n")
)

// handleInputLine处理一整条测试输出线。
// 它必须将该行写入c.output，但可以选择在发出其他事件之前或之后写入。
func (c *Converter) handleInputLine(line []byte) {
	// 最终通过或失败。
	if bytes.Equal(line, bigPass) || bytes.Equal(line, bigFail) || bytes.HasPrefix(line, bigFailErrorPrefix) {
		c.flushReport(0)
		c.output.write(line)
		if bytes.Equal(line, bigPass) {
			c.result = "pass"
		} else {
			c.result = "fail"
		}
		return
	}

	// 完全跳过的测试二进制文件的特殊情况：“？\tpkgname\t[无测试文件]\n”是唯一一行。
	// 将其报告为普通输出，但记住在最终摘要中说skip。
	if bytes.HasPrefix(line, skipLinePrefix) && bytes.HasSuffix(line, skipLineSuffix) && len(c.report) == 0 {
		c.result = "skip"
	}

	// “==运行”
	// “==暂停”
	// “==继续”
	actionColon := false
	origLine := line
	ok := false
	indent := 0
	for _, magic := range updates {
		if bytes.HasPrefix(line, magic) {
			ok = true
			break
		}
	}
	if !ok {
		// ”-通过：“
		// ”-失败：“
		// ”-跳过：“
		// ”-台架：“
		// ”但可能缩进。
		for bytes.HasPrefix(line, fourSpace) {
			line = line[4:]
			indent++
		}
		for _, magic := range reports {
			if bytes.HasPrefix(line, magic) {
				actionColon = true
				ok = true
				break
			}
		}
	}

	// 不是特殊的测试输出线。
	if !ok {
		// 使用
		// 将输出缩进为当前
		// 子测试堆栈的索引，查找生成输出的测试的名称。
		// 如果缩进大于当前子测试的数量
		// 则输出必须包含额外的缩进。我们无法确定哪个子测试产生了这个输出，所以我们默认为
		// 旧行为，即假设最近运行的子测试产生了这个输出。
		if indent > 0 && indent <= len(c.report) {
			c.testName = c.report[indent-1].Test
		}
		c.output.write(origLine)
		return
	}

	// 解析出操作和测试名称。
	i := 0
	if actionColon {
		i = bytes.IndexByte(line, ':') + 1
	}
	if i == 0 {
		i = len(updates[0])
	}
	action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":"))
	name := strings.TrimSpace(string(line[i:]))

	e := &event{Action: action}
	if line[0] == '-' { // 通过或失败报告
		// 解析经过的时间。
		if i := strings.Index(name, " ("); i >= 0 {
			if strings.HasSuffix(name, "s)") {
				t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64)
				if err == nil {
					if c.mode&Timestamp != 0 {
						e.Elapsed = &t
					}
				}
			}
			name = name[:i]
		}
		if len(c.report) < indent {
			// 嵌套深度超出预期。
			// 将此行视为普通输出。
			c.output.write(origLine)
			return
		}
		// 刷新此缩进级别或更深级别的报表。更新。
		c.flushReport(indent)
		e.Test = name
		c.testName = name
		c.report = append(c.report, e)
		c.output.write(origLine)
		return
	}
	// 完成所有挂起的通过/失败报告。
	c.flushReport(0)
	c.testName = name

	if action == "pause" {
		// 对于暂停，我们希望在
		// 传递暂停事件之前编写暂停通知，这样就不会让测试
		// 在暂停后立即生成输出。
		c.output.write(origLine)
	}
	c.writeEvent(e)
	if action != "pause" {
		c.output.write(origLine)
	}

	return
}

// flushReport以>=深度的级别刷新所有挂起的通过/失败报告。
func (c *Converter) flushReport(depth int) {
	c.testName = ""
	for len(c.report) > depth {
		e := c.report[len(c.report)-1]
		c.report = c.report[:len(c.report)-1]
		c.writeEvent(e)
	}
}

// 关闭标志着go测试输出的结束。
// 它刷新所有挂起的输入，然后输出（此时仅部分行）
// 然后发出最终的整体包级通过/失败事件。
func (c *Converter) Close() error {
	c.input.flush()
	c.output.flush()
	if c.result != "" {
		e := &event{Action: c.result}
		if c.mode&Timestamp != 0 {
			dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
			e.Elapsed = &dt
		}
		c.writeEvent(e)
	}
	return nil
}

// WriteOutpEvent使用给定字节写入单个输出事件。
func (c *Converter) writeOutputEvent(out []byte) {
	c.writeEvent(&event{
		Action: "output",
		Output: (*textBytes)(&out),
	})
}

// writeEvent只写一个事件。
// 它添加了包、时间（如果需要）和测试名称（如果需要）。
func (c *Converter) writeEvent(e *event) {
	e.Package = c.pkg
	if c.mode&Timestamp != 0 {
		t := time.Now()
		e.Time = &t
	}
	if e.Test == "" {
		e.Test = c.testName
	}
	js, err := json.Marshal(e)
	if err != nil {
		// 不应发生-事件对json有效。元帅
		c.w.Write([]byte(fmt.Sprintf("testjson internal error: %v\n", err)))
		return
	}
	js = append(js, '\n')
	c.w.Write(js)
}

// lineBuffer是一种I/O缓冲区，通过调用
// 整行或（对于已拆分的长行）行片段上的输入处理回调来对写入做出反应。
// 
// 初始化时，应将b设置为长度为0但容量非零的缓冲区，
// 并将线和部件设置为所需的输入处理器。
// lineBuffer将调用line（x）来表示完全符合cap（b）的任何整行x（包括最后换行）
// 。它将通过
// 调用部分（x）来处理长度超过cap（b）的输入行。这条线将在UTF8边界处分开，
// 最后一次对长线的拆分包括最后一条换行。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊？
type lineBuffer struct {
}

func (l *lineBuffer) write(b []byte) {
	for len(b) > 0 {
		// 将我们能做的复制到b中。
		m := copy(l.b[len(l.b):cap(l.b)], b)
		l.b = l.b[:len(l.b)+m]
		b = b[m:]

		// 处理b中的行。
		i := 0
		for i < len(l.b) {
			j := bytes.IndexByte(l.b[i:], '\n')
			if j < 0 {
				if !l.mid {
					if j := bytes.IndexByte(l.b[i:], '\t'); j >= 0 {
						if isBenchmarkName(bytes.TrimRight(l.b[i:i+j], " ")) {
							l.part(l.b[i : i+j+1])
							l.mid = true
							i += j + 1
						}
					}
				}
				break
			}
			e := i + j + 1
			if l.mid {
				// 找到部分行的结尾。
				l.part(l.b[i:e])
				l.mid = false
			} else {
				// 找到了一整条线。
				l.line(l.b[i:e])
			}
			i = e
		}

		// l.b中剩下的都是一段线。
		if i == 0 && len(l.b) == cap(l.b) {
			// 整个缓冲区是一个片段。
			// 将其作为部分行的开始（或继续）发出。
			t := trimUTF8(l.b)
			l.part(l.b[:t])
			l.b = l.b[:copy(l.b, l.b[t:])]
			l.mid = true
		}

		// 还有更多输入的空间。
		// 把它滑下来，希望能完成这一行。
		if i > 0 {
			l.b = l.b[:copy(l.b, l.b[i:])]
		}
	}
}

// 刷新刷新行缓冲区。
func (l *lineBuffer) flush() {
	if len(l.b) > 0 {
		// 必须是一行，不带\n，所以是一个部分行。
		l.part(l.b)
		l.b = l.b[:0]
	}
}

var benchmark = []byte("Benchmark")

// isBenchmarkName报告b是否是有效的基准名称
// ，它可能会作为基准结果行中的第一个字段出现。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊〕/Trimutf8返回尽可能接近LeN（b）的长度t，以使b〔t〕abcDefg＊＊＊＊＊＊＊
func isBenchmarkName(b []byte) bool {
	if !bytes.HasPrefix(b, benchmark) {
		return false
	}
		return true
	}
	r, _ := utf8.DecodeRune(b[len(benchmark):])
	return !unicode.IsLower(r)
}

// 
// 如果最晚必须在位置i之前拆分一个大文本缓冲区，则
// 在位置trimUTF（b[：i]）处拆分可避免拆分UTF-8序列。
func trimUTF8(b []byte) int {
	// 向后扫描以查找非连续字节。
	for i := 1; i < utf8.UTFMax && i <= len(b); i++ {
		if c := b[len(b)-i]; c&0xc0 != 0x80 {
			switch {
			case c&0xe0 == 0xc0:
				if i < 2 {
					return len(b) - i
				}
			case c&0xf0 == 0xe0:
				if i < 3 {
					return len(b) - i
				}
			case c&0xf8 == 0xf0:
				if i < 4 {
					return len(b) - i
				}
			}
			break
		}
	}
	return len(b)
}
